package com.wang.example.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * nio 服务端代码
 */
public class NIOServer {
    // 通道管理器（选择器），多个用户共用的，所以需要放到这里
    private Selector selector;
    /**
     * 初始化服务端ServerSocketChannel通道，并初始化选择器
     * 获得一个ServerSocket通道，并对该通道做一些初始化的工作
     */
    public void start() throws IOException {
        // 获取ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);
        // 将该通道对应的ServerSocket绑定到port端口
        serverChannel.socket().bind(new InetSocketAddress(9000));
        // 获得一个通道选择器(管理器)
        this.selector = Selector.open();
        /**
         * 将channel注册到selector， SelectionKey.OP_ACCEPT表示注册的是接收连接事件，
         *  当有连接接入时，selector.select()会返回。
         *
         *  一个Selector中可以注册多通道，不同通道在选择器中被封装成为SelectionKey对象。
         *
         * 		***SelectionKey中定义的4种事件 ***
         * 		OP_ACCEPT —— 接收连接事件，表示服务器监听到了客户端连接，服务器可以接收这个连接了
         * 		OP_CONNECT —— 连接就绪事件，表示客户与服务器的连接已经建立成功
         * 		OP_READ —— 读就绪事件，表示通道中已经有了可读的数据，可以执行读操作了
         * 		OP_WRITE —— 写就绪事件，表示已经可以向通道写数据了（通道目前可以用于写操作）
         */
        serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端启动成功...");
        /**
         * 监听selector
         */
        listenSelector();
    }
    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件，如果有，则进行处理
     */
    public void listenSelector() {
        // 轮询访问selector
        while (true) {
            try {
                /**
                 * 事件到达时，继续向下执行；否则,该方法会一直阻塞。
                 * this.selector.select(1000);无论是否有读写事件发生，selector每隔1s被唤醒一次
                 * int select()：阻塞到至少有一个通道在你注册的事件上就绪了。
                 * int select(long timeout)：和select()一样，但最长阻塞时间为timeout毫秒。
                 * int selectNow()：非阻塞，只要有通道就绪就立刻返回。
                 */
                this.selector.select();
                // 获取可读和可连接事件的SelectionKey迭代器
                Iterator<?> iteratorKey = this.selector.selectedKeys().iterator();
                while (iteratorKey.hasNext()) {
                    SelectionKey selectionKey = (SelectionKey) iteratorKey.next();
                    // 删除已选的key,以防重复处理
                    iteratorKey.remove();
                    try {
                        if (selectionKey.isValid()) {//如果selectionKey是合法的
                            if (selectionKey.isAcceptable()) {//处理客户端连接请求事件
                                doHandleConnectEvent(selectionKey);
                            } else if (selectionKey.isReadable()) {//处理读的事件
                                doHandleReadEvent(selectionKey);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        //SelectionKey对象会失效，这意味着Selector再也不会监控与它相关的事件
                        selectionKey.cancel();
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 处理客户端的连接请求
     *
     * @param selectionKey
     * @throws IOException
     */
    private void doHandleConnectEvent(SelectionKey selectionKey) throws IOException {
        System.out.println("新的客户端连接...");
        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
        // 获得和客户端连接的通道
        // 完成该操作意味着完成TCP三次握手，TCP物理链路正式建立
        SocketChannel channel = server.accept();
        // 设置成非阻塞
        channel.configureBlocking(false);
        // 在和客户端连接成功之后，为了可以接收到客户端的信息，
        // 需要将通道注册到selector上，事件为读事件，这样就可以监听该通道是否有可读数据到达
        SelectionKey  newSelectionKey = channel.register(this.selector, SelectionKey.OP_READ);

    }
    /**
     * 处理某个channel的读事件
     *
     * @param selectionKey
     * @throws IOException
     */
    private void doHandleReadEvent(SelectionKey selectionKey) throws IOException {
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);//1kb
        int readData = channel.read(buffer);//将数据读取到缓冲区
        if (readData > 0) {
            String msg = new String(buffer.array(), "UTF-8").trim();
            System.out.println("服务端收到信息：" + msg);
            ByteBuffer writeBackBuffer = ByteBuffer.wrap((msg + " world").getBytes("UTF-8"));
            channel.write(writeBackBuffer);// 将消息发送给客户端
        }
    }
    /**
     * 启动服务端测试
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.start();
    }
}
